package com.kalenchuk.dao.impl;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.springframework.transaction.annotation.Transactional;

import com.kalenchuk.dao.BaseDao;

public abstract class EntityManagerSupport<T> implements BaseDao<T> {

	private static final String DEFAULT_PROPERTY_NAME = "id";
	@PersistenceContext
	protected EntityManager em;
	private Class<T> persistentClass;

	@SuppressWarnings("unchecked")
	public EntityManagerSupport() {
		persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	protected CriteriaBuilder getCriteriaBuilder() {
		return em.getCriteriaBuilder();
	}

	@Transactional
	public T findById(Long id) {
		CriteriaBuilder builder = getCriteriaBuilder();
		CriteriaQuery<T> criteria = builder.createQuery(persistentClass);
		Root<T> rootObject = criteria.from(persistentClass);
		String idPropertyName = getIdPropertyName();
		criteria.where(builder.equal(rootObject.get(idPropertyName), id));
		return em.createQuery(criteria).getSingleResult();
	}

	private String getIdPropertyName() {
		String propertyName = DEFAULT_PROPERTY_NAME;
		Field[] fields = persistentClass.getFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Id.class)) {
				propertyName = field.getName();
				break;
			}
		}
		return propertyName;
	}

	@Transactional
	public List<T> findAll() {
		CriteriaQuery<T> criteria = getCriteriaBuilder().createQuery(
				persistentClass);
		criteria.from(persistentClass);
		List<T> result =  em.createQuery(criteria).getResultList();
		return result;
	}

	@Transactional
	public void save(T object) {
		em.persist(object);

	}

	@Transactional
	public void refresh(T object) {
		em.refresh(object);

	}

	public void delete(T object) {
		em.remove(object);
	}

	@Transactional
	public void shutdown() {
		em.createNativeQuery("SHUTDOWN").executeUpdate();
	}
	
	public void merge(T object) {
		em.merge(object);
	}
}
